package cn.tedu.mall.seckill.service.impl;

import cn.tedu.mall.common.exception.CoolSharkServiceException;
import cn.tedu.mall.common.restful.JsonPage;
import cn.tedu.mall.common.restful.ResponseCode;
import cn.tedu.mall.pojo.product.vo.SpuDetailStandardVO;
import cn.tedu.mall.pojo.product.vo.SpuStandardVO;
import cn.tedu.mall.pojo.seckill.model.SeckillSpu;
import cn.tedu.mall.pojo.seckill.vo.SeckillSpuDetailSimpleVO;
import cn.tedu.mall.pojo.seckill.vo.SeckillSpuVO;
import cn.tedu.mall.product.service.seckill.IForSeckillSpuService;
import cn.tedu.mall.seckill.mapper.SeckillSpuMapper;
import cn.tedu.mall.seckill.service.ISeckillSpuService;
import cn.tedu.mall.seckill.utils.RedisBloomUtils;
import cn.tedu.mall.seckill.utils.SeckillCacheUtils;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.math.RandomUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class SeckillSpuServiceImpl implements ISeckillSpuService {
    @Autowired
    private SeckillSpuMapper seckillSpuMapper;
    @DubboReference
    private IForSeckillSpuService dubboSeckillSpuService;

    //声明一个用于存储spuDetail对象的key常量,在SeckillCacheUtils类中没有声明
    private static final String SECKILL_SPU_DETAIL_VO_PREFIX = "seckill:spu:detail:vo:";
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedisBloomUtils redisBloomUtils;
    //分页查询秒杀列表
    @Override
    public JsonPage<SeckillSpuVO> listSeckillSpus(Integer page, Integer pageSize) {
        //设置分页条件
        PageHelper.startPage(page,pageSize);
        //执行查询
        List<SeckillSpu> seckillSpus = seckillSpuMapper.selectSeckillSpus();
        //声明返回值类型
        List<SeckillSpuVO> seckillSpuVOS = new ArrayList<>();
        //遍历seckillSpus集合
        for (SeckillSpu seckillSpu : seckillSpus) {
            //获取spuId
            Long spuId = seckillSpu.getSpuId();
            //根据spuId查询spu
            SpuStandardVO spuStandardVO = dubboSeckillSpuService.getSpuById(spuId);
            //进行类型转成
            SeckillSpuVO seckillSpuVO = new SeckillSpuVO();
            BeanUtils.copyProperties(spuStandardVO,seckillSpuVO);
            //赋值秒杀属性
            seckillSpuVO.setListPrice(seckillSpu.getListPrice()); //秒杀价格列表
            seckillSpuVO.setStartTime(seckillSpu.getStartTime()); //秒杀开始时间
            seckillSpuVO.setEndTime(seckillSpu.getEndTime()); //秒杀结束时间
            //将赋值完成的对象添加到集合中
            seckillSpuVOS.add(seckillSpuVO);
        }
        //记得返回分页结果
        return JsonPage.restPage(new PageInfo<>(seckillSpuVOS));
    }

    @Override
    public SeckillSpuVO getSeckillSpu(Long spuId) {
        //判断布隆过滤器中是否存在该商品
        //先获取key值
        String todayBloomFilter = SeckillCacheUtils.getBloomFilterKey(LocalDate.now());
        log.info("当前批次的布隆过滤器的可以为{}",todayBloomFilter);
        //不存在，终止业务执行
        if(!redisBloomUtils.bfexists(todayBloomFilter,spuId+""))
            throw new CoolSharkServiceException(ResponseCode.NOT_FOUND,"您要访问的商品不存在");
        //存在，继续执行业务
        //声明一个返回指类型的变量
        SeckillSpuVO seckillSpuVO = null;
        //查询缓存
        //获取对应的key
        String seckillSpuVOKey = SeckillCacheUtils.getSeckillSpuVOKey(spuId);
        if(redisTemplate.hasKey(seckillSpuVOKey)){
            seckillSpuVO = (SeckillSpuVO) redisTemplate.boundValueOps(seckillSpuVOKey).get();
        }else{
            //从数据库中查询
            SeckillSpu seckillSpu = seckillSpuMapper.selectSeckillSpuBySpuId(spuId);
            //判断seckillSpu是否为null，预防布隆过滤器误判
            if(seckillSpu==null)
                throw new CoolSharkServiceException(ResponseCode.NOT_FOUND,"商品不存在");
            //seckillSpu不为null，继续查询具体的spu的信息
            SpuStandardVO spuStandardVO = dubboSeckillSpuService.getSpuById(spuId);
            //进行同名属性赋值
            seckillSpuVO = new SeckillSpuVO();
            BeanUtils.copyProperties(spuStandardVO,seckillSpuVO);
            //将秒杀信息赋值到seckillSpuVO中
            seckillSpuVO.setSeckillListPrice(seckillSpu.getListPrice()); //秒杀价格
            seckillSpuVO.setStartTime(seckillSpu.getStartTime()); //秒杀开始时间
            seckillSpuVO.setEndTime(seckillSpu.getEndTime()); //秒杀结束时间
            //将信息完整的seckillSpuVO对象存储到redis中以备后续使用
            redisTemplate.boundValueOps(seckillSpuVOKey).set(seckillSpuVO,
                    24*60*60 + RandomUtils.nextInt(100),TimeUnit.SECONDS);
        }
        //判断当前时间是否在秒杀时间范围内
        //秒杀时间从seckillSpuVO对象中获取，不再查询数据库
        LocalDateTime startTime = seckillSpuVO.getStartTime();
        LocalDateTime endTime = seckillSpuVO.getEndTime();
        //获取当前系统时间
        LocalDateTime nowTime = LocalDateTime.now();
        //判断当前时间是否大于开始时间并且小于结束时间
        //利用Duration工具类，可以直接对时间进行求差操作
        //结果为正值，就是时间差；结果为负值，返回是negative
        //如果startTime-nowTime返回negative，就说明开始时间小于当前时间
        //如果nowTime-endTime返回negative，就说明当前时间小于结束时间
        //也就是说返回值都是negative时，就是正处于秒杀时间范围内
        Duration afterTime = Duration.between(nowTime, startTime);
        Duration beforeTime = Duration.between(endTime, nowTime);
        if(afterTime.isNegative()&&beforeTime.isNegative()){
            //处于秒杀时间范围内，可以给seckillSpuVO对象中的url进行赋值
            //url要购买的路径：设计为"/seckill/{randCode}"
            //获取随机码
            String randCodeKey = SeckillCacheUtils.getRandCodeKey(spuId);
            int randCode = (int) redisTemplate.boundValueOps(randCodeKey).get();
            seckillSpuVO.setUrl("/seckill/"+randCode);
        }
        //返回seckillSpuVO对象，对象中包含了spu的信息以及url。url中包含随机码，发送到前端进行保存
        //根据随机码进行购买
        return seckillSpuVO;
    }

    //根据spuId查询spu的detail信息
    @Override
    public SeckillSpuDetailSimpleVO getSeckillSpuDetail(Long spuId) {
        //1.先获取对应的key
        String seckillDetailVOKey = SECKILL_SPU_DETAIL_VO_PREFIX+ spuId;
        //2.声明一个返回值类型的对象
        SeckillSpuDetailSimpleVO seckillSpuDetailSimpleVO = null;
        //3.判断redis中是否存在该对象
        if(redisTemplate.hasKey(seckillDetailVOKey)){
            seckillSpuDetailSimpleVO = (SeckillSpuDetailSimpleVO) redisTemplate.boundValueOps(seckillDetailVOKey).get();
        }else{
            //4.缓存中没有，从数据库中查询
            SpuDetailStandardVO spuDetailStandardVO = dubboSeckillSpuService.getSpuDetailById(spuId);
            //5.同名属性赋值
            seckillSpuDetailSimpleVO = new SeckillSpuDetailSimpleVO();
            BeanUtils.copyProperties(spuDetailStandardVO,seckillSpuDetailSimpleVO);
            //6.存入redis中，以备后续使用
            redisTemplate.boundValueOps(seckillDetailVOKey).set(seckillSpuDetailSimpleVO,
                    24*60*60 + RandomUtils.nextInt(100), TimeUnit.SECONDS);
        }
        //千万要记得返回
        return seckillSpuDetailSimpleVO;
    }
}
